package org.amse.korovkin.ciphering.model.cipher.monocipher;
import java.io.*;
import java.util.zip.DataFormatException;
import org.amse.korovkin.ciphering.model.Analyzator;
import org.amse.korovkin.ciphering.model.BigramFrequency;
import org.amse.korovkin.ciphering.model.IModel;
import org.amse.korovkin.ciphering.model.Model;
import org.amse.korovkin.ciphering.model.PossibilityTable;
import org.amse.korovkin.ciphering.model.Statistic;
import org.amse.korovkin.ciphering.model.TableOfFrequency;
import org.amse.korovkin.ciphering.model.cipher.util.TextWork;
import org.amse.korovkin.ciphering.view.*;

public class MonoAttacker {
    public static final int SUCCESSFUL = 1;
    public static final int STOPPED = -1;
    public static volatile boolean stopFlag;
    public static volatile boolean pauseFlag;
    private static long counter = 0;
    private static String inFileName;
    private static String outFileName;
    private static ChangeTable currentTable;
    private static PossibilityTable posTable;
    private static ChangeTable trueKey = new ChangeTable();
    //BigramFrequency of Encrupted text
    private static BigramFrequency EncryptBiFrec = new BigramFrequency();
    //BigramFrequency transposed from encrypted Bigram by possibility ChangeTable
    private static BigramFrequency currentBigram = new BigramFrequency();
    //EtalonFrequency of symbols
    private static TableOfFrequency standartFrec = new TableOfFrequency();
    //EtalonBigramFrequency
    private static BigramFrequency biStandart = new BigramFrequency();
    private static double minBiPenalty = Double.MAX_VALUE;
    private static int minPenalty = Integer.MAX_VALUE;
    //Counter of the best variant
    private static long trueCounter = 0;
    // percent of completed work
    private static int percent = -1;
    // Amount of variants in PossibilityTable
    private static long allWork;
    private static long complitedWork = 0;

    private static int rec(int i, int j)
    throws IOException, InterruptedException, ArrayIndexOutOfBoundsException{
        currentTable.add((char) posTable.getChar(i), (char) posTable.getPossibility(i, j));
        if (posTable.getAmount() > i + 1) {
            if (posTable.getLine(i + 1) > 0) {
                for (int k = 0; k < posTable.getLine(i + 1); k++) {
                    char ch = (char) posTable.getPossibility(i + 1, k);
                    if (currentTable.getLetter1(ch) == -1) {
                        rec(i + 1, k);
                        currentTable.removeLast();
                        if (stopFlag) {
                            return STOPPED;
                        }
                    }
                }
                // If PossibilityTable has Empty Lines
            } else {
                System.out.println("Doesn't any variants for encrypt\n" +
                    "(PossibilityTable has empty lines)\n" +
                    "The reason may be failure combination of etalonStatistic and encryptedStatistic");
            }
            // ChangeTable completed - we have one more variant
        } else {
            counter++;
            /*if (currentTable.getLetter2('n') == (int)'o') {
                System.out.println(currentTable.toString());
            }*/
            // Print COMPLETED WORK
            if ((counter * 100 / allWork) > percent) {
                percent = (int)(counter * 100 / allWork);
                System.out.println("Completed work: " + percent + "%");
                if (percent != 0) {
                    IModel model = Model.getInstance();
                    model.refresh(Model.ATTACK_JDIALOG);
                }
            }          
            currentBigram.transpose(EncryptBiFrec, currentTable);
            double biPenalty = BigramFrequency.compare(biStandart, currentBigram);
            if (biPenalty != -1 || trueCounter == 0) {
                // DECRYPT by ChangeTable and ANALYSE resulting text
                MonoCipher.getInstance().decrypt(inFileName, outFileName + counter, currentTable);
                int penalty = Analyzator.analyze(outFileName + counter);
                if (trueCounter == 0) {
                     trueKey = currentTable.clone();
                     trueCounter = counter;
                     //System.out.println(trueKey.toString());
                }
                // Penalty is not very big, perhaps it is TRUE variant
                if ((penalty < minPenalty && penalty != -1)) {
                    trueKey = currentTable.clone();
                    minPenalty = penalty;
                    Analyzator.setMinPenalty(penalty);
                    minBiPenalty = biPenalty;
                    Analyzator.setMinBiPenalty(biPenalty);
                    File f = new File(outFileName + trueCounter);
                    f.delete();
                    trueCounter = counter;                    
                } else {
                    if (counter != 1) {
                        File f = new File(outFileName + counter);
                        f.delete();
                    }
                }
            }
            if (stopFlag) {
                return STOPPED;
            }
            synchronized(AttackJDialog.stopLock) {
                if (pauseFlag) {
                    AttackJDialog.stopLock.wait();
                }
            }
        }
        return SUCCESSFUL;
    }
    
    public static int attack(String inFile, String outFile)
    throws IOException, DataFormatException, InterruptedException{
        inFile = TextWork.cutComma(inFile);
        inFileName = inFile;
        outFileName = outFile;
        counter = 0;
        percent = -1;
        pauseFlag = false;
        stopFlag = false;
        trueCounter = 0;
        minPenalty = Integer.MAX_VALUE;
        minBiPenalty = Double.MAX_VALUE;
        TableOfFrequency tableOfFrec = new TableOfFrequency();

        tableOfFrec = Statistic.calcFrequency(inFile);
        tableOfFrec.sortFrequences();
        EncryptBiFrec = Statistic.calcBigramFrequency(inFile);
        EncryptBiFrec.sortLetters();

        try {
        standartFrec = Statistic.loadEtalonFrequency();
        standartFrec.sortFrequences();
        biStandart = Statistic.loadEtalonBigram();
        } catch (IOException e) {
            e.printStackTrace();
            throw new IOException(e.getMessage() + "Please choose StatisticFile correctly. ");
        }

        posTable = new PossibilityTable(standartFrec);
        posTable.create(tableOfFrec);
        posTable.reform();
        System.out.println(posTable.toString());

        currentTable = new ChangeTable();
        allWork = posTable.getAmountOfVariants(new ChangeTable());
        System.out.println(allWork);

        int result = rec(0, 0);

        File f1 = new File(outFileName);
        File f2 = new File(outFileName + trueCounter);
        f2.renameTo(f1);
        f2.delete();
        return result;
    }
    public static void setStopFlag(boolean flag) {
        stopFlag = flag;
    }
    public static void setPauseFlag(boolean flag) {
        pauseFlag = flag;
    }
    public static String getMomentFilePath() {
        long x = trueCounter;
        return (outFileName + x);
    }
    public static void setTrueChoices(char[] letters) throws DataFormatException, InterruptedException {
        
        for (int i = 0; i < letters.length; i++) {
            char letter2 = (char)trueKey.getLetter2(letters[i]);
            for (int j = 0; j < posTable.getAmount(); j++) {
                if (posTable.getChar(j) == letters[i]) {
                    int buf = posTable.getLine(j);
                    for (int k = 0; k < buf; k++) {
                        //posTable.setPossibility(j, k, letter2);
                        //currentTable.setLetter2(letters[i], letter2);
                        posTable.deletePossibility(j, 0);
                    }
                    posTable.addPossibility(j, letter2);
                    break;
                }
            }
        }
        posTable.reform();
        System.out.println(posTable.toString());
        refresh();
    }
    private static void refresh() throws DataFormatException, InterruptedException {
        long buf = allWork;
        allWork = posTable.getAmountOfVariants(currentTable);
        System.out.println(allWork);
        counter = (int) (counter * allWork / buf);
        //____________________________________________________
        System.out.println("Completed work: " + percent + "%");
    }
    public static void setComplitedWork(long work) {
        complitedWork = work;
    }
    public static int getProgress() {
        return percent;
    }
}
